package com.agent.vpsserver.common.filter;

import cn.hutool.core.net.URLDecoder;
import com.agent.vpsserver.common.dto.AdminUserCacheDTO;
import com.agent.vpsserver.common.dto.GlobalConstant;
import com.agent.vpsserver.common.dto.Result;
import com.agent.vpsserver.common.enmus.EResponseCode;
import com.agent.vpsserver.common.exception.BusinessException;
import com.agent.vpsserver.common.exception.EOauthRespCode;
import com.agent.vpsserver.common.utils.common.EnvUtils;
import com.agent.vpsserver.common.utils.common.JsonUtils;
import com.agent.vpsserver.common.utils.common.StringUtils;
import com.agent.vpsserver.common.utils.sign.SignUtils;
import com.agent.vpsserver.modules.admin.service.AdminUserService;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Charsets;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;

/**
 * Created by gc on 2021/5/26.
 *
 * @author : gc
 * <p>
 * Description: request请求过滤器
 **/
@Component
@Slf4j
@RequiredArgsConstructor
public class HttpServletRequestReplacedFilter extends OncePerRequestFilter {

    @Value("${spring.profiles.active}")
    private String profiles;

    private final AdminUserService adminUserService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //跨域的header设置
        response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", request.getMethod());
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
        // 处理乱码
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        // 忽略OPTIONS请求拦截
        if (request.getMethod().equalsIgnoreCase(RequestMethod.OPTIONS.toString())) {
            filterChain.doFilter(request, response);
            return;
        }
        if (request.getServletPath().startsWith("/admin") || request.getServletPath().startsWith("/api")) {
            this.sign(request, response, filterChain);
        } else {
            filterChain.doFilter(request, response);
        }
    }

    /**
     * 签名校验
     *
     * @param request
     * @param response
     * @param filterChain
     */
    private void sign(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        try {
            // 校验带请求头
            if (StringUtils.isBlank(request.getHeader(HttpHeaders.AUTHORIZATION))) {
                log.error("缺少Bearer请求头，请求方式:{}, 请求地址:{}", request.getMethod(), request.getServletPath());
                throw new BusinessException(EResponseCode.AUTHORIZATION_BEARER_ERROR);
            }

            // 校验请求头格式
            String authorization = Objects.requireNonNull(request.getHeader(HttpHeaders.AUTHORIZATION)).replaceFirst("^(Bearer) *", "");
            if (StringUtils.isBlank(authorization)) {
                throw new BusinessException(EResponseCode.BEARER_PARAM_ERROR);
            }
            authorization = URLDecoder.decode(authorization, Charsets.UTF_8);

            // 请求头参数
            JSONObject headObject = JsonUtils.parseObject(authorization);
            Map<String, Object> paramsMap = new HashMap<>(headObject);

            // 有token的情况下，设置用户缓存信息
            String token = headObject.getString("token");

            // 获取请求体内容
            String methodType = request.getMethod().toUpperCase();
            if (("POST".equals(methodType)) || ("DELETE".equals(methodType)) || ("PUT".equals(methodType))) {
                String data = this.getInputStream(request);
                // 非json内容不处理
                if (JsonUtils.isJsonString(data)) {
                    // 通过请求头的token获取用户信息设置进请求体
                    paramsMap.putAll(JsonUtils.parseObject(data, Map.class));
                }
            } else {
                paramsMap.putAll(this.getParameterMap(request));
            }

            log.info("url:{}，请求参数:{}，contentType:{},methodType:{}", request.getServletPath(), JsonUtils.toJSONString(paramsMap), request.getHeader(HttpHeaders.CONTENT_TYPE), methodType);
            // 加密校验
            if (!("test".equals(profiles) || "dev".equals(profiles)) || !headObject.getBooleanValue("noSign")) {
                this.validate(paramsMap, token);
            }

            // 设置用户信息
            if (StringUtils.isNotBlank(token)) {
                AdminUserCacheDTO userCacheDTO = adminUserService.get(token);
                if (userCacheDTO == null) {
                    throw new BusinessException(EOauthRespCode.SESSION_ID_INVALID);
                }
                // 设置用户信息
                paramsMap.put("adminUserCacheDTO", userCacheDTO);
            }
            ServletRequest requestWrapper = new RequestWrapper(request, paramsMap);
            filterChain.doFilter(requestWrapper, response);
        } catch (Exception e) {
            log.error("过滤器发现异常", e);
            response.setStatus(HttpServletResponse.SC_OK);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setHeader("Content-Type", "textml;charset=utf-8");
            ServletOutputStream out = response.getOutputStream();
            // 自定义的异常信息需要以200的方式返回
            if (e instanceof BusinessException) {
                BusinessException businessException = (BusinessException) e;
                out.write(JsonUtils.toJSONString(Result.fail(businessException.getCode(), businessException.getMessage())).getBytes());
            } else {
                out.write(JsonUtils.toJSONString(Result.fail(EResponseCode.SYSTEM_BUSY_ERROR)).getBytes());
            }
            out.close();
            out.flush();
        }
    }

    /**
     * 获取请求流参数
     *
     * @param request
     * @return
     */
    private String getInputStream(HttpServletRequest request) {
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        try (InputStream inputStream = request.getInputStream()) {
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            }
        } catch (IOException ex) {
            log.error("解析请求流参数异常", ex);
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    log.error("释放缓冲流异常", e);
                }
            }
        }
        return stringBuilder.toString();
    }

    /**
     * 将request中的Mep转换成map
     *
     * @param request
     * @return
     */
    public Map<String, Object> getParameterMap(HttpServletRequest request) {
        // 参数Map
        Map<String, String[]> properties = request.getParameterMap();
        // 返回值Map
        Map<String, Object> returnMap = new HashMap<>();
        Iterator<Map.Entry<String, String[]>> entries = properties.entrySet().iterator();
        Map.Entry<String, String[]> entry;
        String name = "";
        String value = "";
        while (entries.hasNext()) {
            entry = entries.next();
            name = entry.getKey();
            String[] valueObj = entry.getValue();
            if (null == valueObj) {
                value = "";
            } else if (valueObj.length > 1) {
                value = Arrays.toString(valueObj);
            } else {
                value = valueObj[0];
            }
            returnMap.put(name, value);
        }
        return returnMap;
    }

    /**
     * 加密校验
     *
     * @param paramsMap 请求参数
     * @param secKey    密钥
     */
    private void validate(Map<String, Object> paramsMap, String secKey) {
        boolean validSign = SignUtils.isValidSign(paramsMap, secKey);
        if (!validSign) {
            String profiles = EnvUtils.get("spring.profiles.active");
            if (GlobalConstant.ENV_DEV.equals(profiles) || GlobalConstant.ENV_TEST.equals(profiles)) {
                String signStr = SignUtils.getSignStr(paramsMap, secKey);
                throw new BusinessException(EResponseCode.SYSTEM_SIGN_ERROR.getCode(), "######服务端拼接kv：" + SignUtils.signOriginal(paramsMap, secKey) + "\n###服务端加密sign:" + signStr);
            }
            throw new BusinessException(EResponseCode.SYSTEM_SIGN_ERROR);
        }
    }
}